Что такое repository pattern в NestJS при работе с TypeORM?

JuniorNestJS · Backend·Обновлено 15 сентября 2026
Коротко
Repository Pattern — это слой абстракции между бизнес-логикой и базой данных, который в NestJS реализуется через класс Repository из TypeORM, инкапсулируя запросы к БД и предоставляя сервисам единый интерфейс для работы с сущностями.

Суть паттерна

Repository Pattern — это паттерн проектирования, который выносит всю логику доступа к данным в отдельный слой (репозиторий), скрывая от бизнес-логики детали того, как именно данные хранятся и извлекаются. Сервисы работают с сущностями через понятный объектно-ориентированный интерфейс, не зная о SQL-запросах, драйвере БД или структуре таблиц.

Как это реализовано в TypeORM

TypeORM предоставляет готовую реализацию паттерна — класс Repository<Entity>. Для каждой сущности можно получить репозиторий с методами find, findOne, save, delete, update и построителем запросов QueryBuilder.

Чтобы использовать репозиторий в NestJS, нужно:

  1. Зарегистрировать сущность в модуле через TypeOrmModule.forFeature([User]).
  2. Внедрить репозиторий в сервис через декоратор @InjectRepository(User).

Пример базового использования

Смотри codeExamples — там показано подключение модуля, внедрение репозитория и базовые CRUD-операции.

Зачем нужен этот слой абстракции

  • Разделение ответственности — сервис занимается бизнес-логикой, репозиторий отвечает только за доступ к данным.
  • Тестируемость — в unit-тестах репозиторий легко подменить моком, не поднимая реальную БД.
  • Единая точка сложных запросов — специфичные выборки, джойны, фильтрацию удобно собирать в одном месте, а не размазывать по сервисам.
  • Гибкость — теоретически источник данных можно заменить (другая БД, внешний API), не переписывая бизнес-логику, если интерфейс репозитория сохранён.

Кастомные репозитории

Когда стандартных методов Repository не хватает, в NestJS принято создавать кастомный провайдер поверх репозитория — отдельный класс, который инжектит Repository<Entity> и добавляет собственные методы (например, findActiveUsers или findByEmailWithRoles). Начиная с TypeORM 0.3.x подход extends Repository через @EntityRepository устарел, вместо этого используют обычный сервис-обёртку или Repository.extend.

Отличие от простого использования Repository напрямую в контроллере

Хотя технически можно инжектировать Repository прямо в контроллер, хорошей практикой считается делать это только внутри сервиса. Так контроллер остаётся тонким и отвечает только за HTTP-слой, а вся работа с данными и бизнес-правила инкапсулированы в сервисе поверх репозитория.

Итог

Repository Pattern в связке NestJS и TypeORM — это стандартный слой между бизнес-логикой и базой данных, реализованный через Repository<Entity>, который подключается через модуль (forFeature) и внедряется через Dependency Injection (@InjectRepository). Он упрощает тестирование, разделяет ответственность и даёт единое место для запросов к конкретной сущности.

Что хочет услышать интервьюер

Кандидат объясняет, что Repository Pattern отделяет доступ к данным от бизнес-логики

Знает, как подключить сущность через TypeOrmModule.forFeature и внедрить репозиторий через @InjectRepository

Понимает практическую пользу: упрощение тестирования, единая точка для запросов

Может привести пример кастомного репозитория для сложных запросов

Понимает, что репозиторий обычно используется внутри сервиса, а не напрямую в контроллере

Пример: Регистрация сущности и внедрение репозитория

// users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])], // регистрируем сущность в модуле
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>, // репозиторий внедряется через DI
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: number): Promise<User | null> {
    return this.usersRepository.findOneBy({ id });
  }

  create(dto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(dto);
    return this.usersRepository.save(user);
  }
}

Пример: Кастомный репозиторий со специфичным запросом

// users.repository.ts
@Injectable()
export class UsersRepository {
  constructor(
    @InjectRepository(User)
    private readonly repo: Repository<User>,
  ) {}

  // специфичный запрос, который не хочется размазывать по сервисам
  findActiveUsers(): Promise<User[]> {
    return this.repo
      .createQueryBuilder('user')
      .where('user.isActive = :isActive', { isActive: true })
      .getMany();
  }
}

Типичные ошибки

Путают Repository Pattern с самим ORM, не понимая, что это паттерн проектирования, а TypeORM — его конкретная реализация

Инжектируют репозиторий прямо в контроллер, минуя сервисный слой

Не знают, что нужно зарегистрировать сущность через TypeOrmModule.forFeature перед использованием @InjectRepository

Используют устаревший декоратор @EntityRepository, не зная, что он deprecated в новых версиях TypeORM

Не могут объяснить, зачем нужен этот слой абстракции, если можно просто писать SQL-запросы напрямую

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее